
import re


PRINTASCIICODES = set([x for x in range(ord(' '), ord('~') + 1)])


class UnicodeEntry:
    def __init__(self, code, unicode, unicodename = None):
        self.code = code
        self.unicode = unicode
        self.unicodename = unicodename

    def __str__(self):
        return '{0}\t{1}\t#{2}'.format(self.code, self.unicode, self.unicodename)


class UnicodeMap:
    def __init__(self):
        self.toUnicodeMap = dict()
        self.fromUnicodeMap = dict()

    def addEntry(self, mapline = None, entry = None):
        if mapline is not None:
            m = re.search(r'^(0x?)?([\da-fA-F]+)\s0x([\da-fA-F]+)(\s+#\s*(.*))?$',
                    mapline)

            if m == None:
                return

            if m.group(1) == "0":
                code_radix = 8
            elif m.group(1) == None:
                code_radix = 10
            else:
                code_radix = 16

            uentry = UnicodeEntry(
                    int(m.group(2), code_radix), int(m.group(3), 16), m.group(5))
        elif entry is not None:
            uentry = entry
        else:
            return

        if not uentry.code in self.toUnicodeMap:
            self.toUnicodeMap[uentry.code] = uentry

        if not uentry.unicode in self.fromUnicodeMap:
            self.fromUnicodeMap[uentry.unicode] = uentry

    def getUnicode(self, code):
        if code in self.toUnicodeMap:
            return self.toUnicodeMap[code].unicode
        return None

    def getCode(self, unicode):
        if unicode in self.fromUnicodeMap:
            return self.fromUnicodeMap[unicode].code
        return None

    def existCode(self, code):
        return code in self.toUnicodeMap

    def existUnicode(self, unicode):
        return unicode in self.fromUnicodeMap

    def sortedByCode(self):
        return [self.ToUnicodeMap[code] for code in sorted(self.ToUnicodeMap)]

    def sortedByUnicode(self):
        return [self.fromUnicodeMap[unicode]
                for unicode in sorted(self.fromUnicodeMap)]


(
  KEYACTION_SYMBOL,
  KEYACTION_NAME,
) = range(2)

class KeyAction:
    def __init__(self, symbol=None, name=None, keyaction=None):
        if keyaction != None:
            self.type = keyaction.type
            self.symbol = keyaction.symbol
            self.name = keyaction.name
        else:
            if symbol != None:
                self.type = KEYACTION_SYMBOL
            elif name != None:
                self.type = KEYACTION_NAME
            else:
                self.type = None
            self.symbol = symbol
            self.name = name

    def __str__(self):
        if self.type == KEYACTION_SYMBOL:
            if self.symbol in PRINTASCIICODES:
                return "'{0}'".format(chr(self.symbol))
            else:
                return '{0:#06x}'.format(self.symbol)
        elif self.type == KEYACTION_NAME:
            return '{0}'.format(self.name)

        return ''

    def remap(self, remap):
        action = KeyAction(keyaction=self)
        if action.type == KEYACTION_SYMBOL and action.symbol >= 128 and \
                remap != None and remap.existCode(action.symbol):
            action.symbol = remap.getUnicode(action.symbol)
        return action 


class KeyEntry:
    def __init__(self, scancode=None, actions=None, lockstate=None, keyline=None):
        if keyline != None:
            m = re.search(r'^\s*(\d+)\s+(.*)$', keyline)

            if m == None:
                self.scancode = None
                return

            self.scancode = int(m.group(1))
            line = m.group(2)

            self.actions = []

            for i in range(8):
                m = re.search(r'^(\'(.)\'|(0x?)?(\d{3})|(\S[\d\S]+))\s+(.*)$', line)

                if m.group(2) != None:
                    symbol = ord(m.group(2))
                elif m.group(4) != None:
                    if m.group(3) == "0":
                        num_radix = 8
                    elif m.group(3) == None:
                        num_radix = 10
                    else:
                        num_radix = 16

                    symbol = int(m.group(4), num_radix)
                else:
                    symbol = None

                line = m.group(6)

                self.actions.append(KeyAction(symbol, m.group(5)))

            self.lockstate = line
        else:
            self.scancode = scancode
            if actions != None:
                self.actions = actions
            else:
                self.actions = []
            self.lockstate = lockstate

    def __str__(self):
        if self.scancode == None:
            return ''
        res = '  {0:03d}   '.format(self.scancode)
        for a in self.actions:
            res += '{0:<6s} '.format(a)
        res += ' {0}'.format(self.lockstate)
        return res

    def remap(self, newmap):
        entry = KeyEntry(self.scancode, lockstate=self.lockstate)
        for a in self.actions:
            entry.actions.append(a.remap(newmap))
        return entry


class AccentPair:
    def __init__(self, char, code):
        self.char = char
        self.code = code

    def __str__(self):
        return "( '{0}' {1:#06x} )".format(self.char, self.code)

    def remap(self, newmap):
        return AccentPair(self.char, newmap.getUnicode(self.code))


class AccentEntry:
    def __init__(self, name = None, char = None, mapline = None):
        if mapline != None:
            m = re.search(r'^\s*((d[a-z]+)\s+(\'(.)\'|(\d+))\s+)?(.*)$', mapline)

            if m == None or m.group(2) == None and len(m.group(6)) == 0:
                self.name = None
                self.char = None
                self.maplist = None
                return

            self.name = m.group(2)
            if self.name == None:
                self.name = ''

            if m.group(4) != None:
                self.char = ord(m.group(4))
            elif m.group(5) != None:
                self.char = int(m.group(5))
            else:
                self.char = None
            self.maplist = list()
            pairs = m.group(6)

            while pairs != None and len(pairs) > 0:
                m = re.search(r'^\(\s*\'(.)\'\s*(0x?)?(\d+)\s*\)(\s*(.*))?$', pairs)

                if m == None:
                    if len(self.name) == 0:
                        self.name = None
                        self.char = None
                        self.maplist = None
                    break

                if m.group(2) == None:
                    num_radix = 10
                else:
                    num_radix = 16

                accentpair = AccentPair(m.group(1), int(m.group(3), num_radix))
                self.maplist.append(accentpair)

                pairs = m.group(5)
        else:
            self.name = name
            self.char = char
            self.maplist = []

    def __str__(self):
        if self.char != None:
            if self.char in PRINTASCIICODES:
                char = "'{0}'".format(chr(self.char))
            else:
                char = '{0:#06x}'.format(self.char)
        else:
            char = ''
        res = '  {0:<5s} {1:<6s}'.format(self.name, char)
        for pair in self.maplist:
            res += ' {0}'.format(pair)
        return res

    def remap(self, newmap):
        if self.char != None:
            newchar = newmap.getUnicode(self.char)
        else:
            newchar = None
        accent = AccentEntry(self.name, newchar)
        for p in self.maplist:
            accent.maplist.append(p.remap(newmap))
        return accent
